package cn.picclife.mwx.common.core.util.xml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Throwables;
import com.google.common.collect.Lists;

/**
 * XML文档组装和解析工具类
 */
public class XmlUtils {
	public static Logger logger = LoggerFactory.getLogger(XmlUtils.class);
	
	/** 缺省字符集 **/
	public static final String DEFAULT_ENCODING = "UTF-8";

	private XmlUtils() {
	}

	/**
	 * 通过Reader读取Document文档 如果encodingStr为null或是""，那么采用缺省编码GB2312
	 * 
	 * @param in Reader器
	 * @param encoding 编码器
	 * @return documment
	 */
	public static Document getDocument(Reader in, String encoding) {
		try {
			if (encoding == null || encoding.equals("")) {
				encoding = DEFAULT_ENCODING;
			}
			SAXReader reader = new SAXReader();
			Document document = reader.read(in, encoding);
			return document;
		} catch (Exception ex) {
			throw Throwables.propagate(ex);
		}
	}

	/**
	 * 直接从字符串得到XML的Document
	 * 
	 * @param source 把一个字符串文本转化为XML的Document对象
	 * @param encoding 编码器
	 * @return Document
	 */
	public static Document getDocument(String source, String encoding) {
		return getDocument(new StringReader(source), encoding);
	}

	/**
	 * 给定输入流读取XML的Document
	 * 
	 * @param inputSource 输入源
	 * @param encoding 编码器
	 * @return document
	 */
	public static Document getDocument(InputStream inputSource, String encoding) {
		try {
			if (encoding == null || encoding.equals("")) {
				encoding = DEFAULT_ENCODING;
			}
			SAXReader reader = new SAXReader();
			Document document = reader.read(inputSource, encoding);
			return document;
		} catch (Exception ex) {
			throw Throwables.propagate(ex);
		}
	}

	/**
	 * 新建文档
	 * 
	 * @return Document 文档节点
	 * @throws RuntimeException
	 */
	public static Document createDocument() {
		DocumentFactory factory = new DocumentFactory();
		Document document = factory.createDocument();
		return document;
	}
	
	/**
	 * 新Element节点，值为String类型
	 * 
	 * @param parent 父节点
	 * @param name 新节点名称
	 * @param value 新节点值
	 * @return element
	 * @throws RuntimeException
	 */
	public static Element addChild(Element parent, String name, String value) {
		Element element = parent.addElement(new QName(name, parent.getNamespace()));
		if (value != null) {
			element.addText(value);
		}
		return element;
	}
	
	/** 
	* @Description: 根据xpath查找节点，并设置节点的值
	* @param currentElement
	* @param xPath
	* @param value void 
	* @throws 
	*/
	public static void setValue(Element currentElement, String xPath, String value) {
		Element element = (Element) currentElement.selectSingleNode(xPath);
		if (element == null) {
			throw new RuntimeException(String.format("xml setvalue failed,no element %s %s", currentElement.getName(), xPath));
		}
		element.setText(value);
	}

	public static void setAttribute(Element currentElement, String xPath, String propertyName, String propertyValue) {
		Element element = (Element) currentElement.selectSingleNode(xPath);
		if (element == null) {
			throw new RuntimeException(String.format("xml setattribute failed,no element [%s] [%s]", currentElement.getPath(), xPath));
		}

		element.addAttribute(propertyName, propertyValue);
	}

	/**
	 * 把XML文档转化为String返回
	 * 
	 * @param document 要转化的XML的Document
	 * @param encoding 编码类型
	 * @return <code>String</code>
	 * @throws RuntimeException 如果有任何异常转化为该异常输出
	 */
	/*public static String toPrettyString(Document document, String encoding) {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		toStringInternal(document, stream, encoding, true);
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return stream.toString();
	}*/

	/**
	 * 把XML文档转化为String返回
	 * 
	 * @param document 要转化的XML的Document
	 * @param encoding 编码类型
	 * @return <code>String</code>
	 * @throws RuntimeException 如果有任何异常转化为该异常输出
	 */
	/*public static String toCompactString(Document document, String encoding) {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		toStringInternal(document, stream, encoding, false);
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return stream.toString();
	}*/

	@SuppressWarnings("unchecked")
	public static List<Element> getElements(Element currentElement, String xPath) {
		return currentElement.selectNodes(xPath);
	}

	public static Element getElement(Element currentElement, String xPath) {
		return (Element) currentElement.selectSingleNode(xPath);
	}

	/** 
	* TODO
	*
	* @param currentElement
	* @param xPath
	* @return String 
	* @throws 
	*/
	public static String getValue(Element currentElement, String xPath) {
		Element element = (Element) currentElement.selectSingleNode(xPath);
		if (element == null) {
			return null;
		}

		return element.getText();
	}
	/**
	 * 得到给定节点的值,以字符串返回
	 * 
	 * @param element 节点
	 * @param optional 是否是可选的
	 * @return 值
	 */
	public static String getValue(Element element, boolean optional) {
		String content = element.getText();
		if (content == null && !optional)
			throw new RuntimeException(element.getName() + " element: content expected.");
		else
			return content;
	}
	
	/**
	 * 得到某个节点下的属性信息
	 * 
	 * @param element 节点
	 * @param name 属性名
	 * @param optional 是否是可选的
	 * @return 值
	 */
	public static String getAttribute(Element element, String name, boolean optional) {
		Attribute attr = element.attribute(name);
		if (attr == null && !optional)
			throw new RuntimeException("Attribute " + name + " of " + element.getName() + " expected.");
		else
			return attr.getValue();
	}
	
	/** 
	* @Title: getAttribute 
	* @Description: 根据xPath查找结点的attribute
	* @param currentElement
	* @param xPath
	* @param propertyName
	* @return String 
	* @throws 
	*/
	public static String getAttribute(Element currentElement, String xPath, String propertyName) {
		Element element = (Element) currentElement.selectSingleNode(xPath);
		if (element == null) {
			return null;
		}

		return element.attributeValue(propertyName);
	}
	
	/** 
	* @Title: getAttribute 
	* @Description: 根据xPath查找结点的attributes
	* @param currentElement
	* @param xPath
	* @param propertyName
	* @return String 
	* @throws 
	*/
	public static List<String> getAttributes(Element currentElement, String xPath, String propertyName) {
		List<String> attributes = Lists.newArrayList();
		
		@SuppressWarnings("unchecked")
		List<Element> elements = currentElement.selectNodes(xPath);
		for(Element aElement:elements){
			String property = getAttribute(aElement,propertyName,true);
			attributes.add(property);
		}
		
		return attributes;
	}
	
	/**
	 * 将xpath中的一个占位符替换成变量
	 * @param xPath 含有占位符节点路径 
	 * @param para  变量值
	 * @return 返回完整路径
	 */
	public static String getPathByPara(String xPath,String param){
		String path=xPath.replaceFirst("[?]", param);
		return path;
	}
	

	/**
	 * 把XML的Document转化为java.io.Writer输出流 不支持给定Schema文件的校验
	 * 
	 * @param document XML文档
	 * @param outStream 输出写入器
	 * @param encoding 编码类型
	 * @throws RuntimeException 如果有任何异常转化为该异常输出
	 */
	/*private static void toStringInternal(Document document, OutputStream outStream, String encoding, Boolean isPretty) {
		//
		OutputFormat outformat = null;
		if (isPretty)
			outformat = OutputFormat.createPrettyPrint();
		else
			outformat = OutputFormat.createCompactFormat();

		if (encoding == null || encoding.trim().equals("")) {
			encoding = DEFAULT_ENCODING;
		}
		// 设置编码类型
		outformat.setEncoding(encoding);
		XMLWriter xmlWriter = null;
		try {
			xmlWriter = new XMLWriter(outStream, outformat);
			xmlWriter.write(document);
			xmlWriter.flush();
		} catch (IOException ex) {
			throw Throwables.propagate(ex);
		} finally {
			if (xmlWriter != null) {
				try {
					xmlWriter.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}*/
}